{% extends 'base.html' %}

{% block content %}

<h1>作用域和闭包</h1>
<h2>作用域</h2>
<p>
    储存和访问变量值的能力将<b>状态</b> 带给了程序
</p>
<p>
    一套设计良好的规则来存储变量， 并且之后可以方便地找到这些变量。
这套规则被称为作用域
</p>

<dl>
    <dt>RHS = LHS</dt>
    <dd>retrive his source</dd>
    <dd>ReferenceError 同作用域判别失败相关</dd>
    <dd> TypeError 则代表作用域判别成功了， 但是对
        结果的操作是非法或不合理的</dd>
</dl>
<dl>
    <dt>作用域是一套规则， 用于确定在何处以及如何查找变量（标识符）。</dt>
    <dd> 如果查找的目的是对变量进行赋值(或传入参数的操作)， 那么就会使用 LHS 查询； </dd>
    <dd>如果目的是获取变量的值， 就会使用 RHS 查询。</dd>
</dl>
<h2>函数作用域和块作用域</h2>
<dl>
    <dt>IIFE</dt>
    <dd>代表立即执行函数表达式
        （Immediately Invoked Function Expression） </dd>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                var a = 2;
                (function IIFE() {
                    var a = 3;
                    console.log( a ); // 3
                })();
                console.log( a ); // 2
            </code>
        </pre>
    </dd>
    <dd>
        <pre class="language-javascript line-number">
            <code>
            // IIFE 的另一个非常普遍的进阶用法是把它们当作函数调用并传递参数进去
            // 将 window 对象的引用(实参)传递进去， 但将参数命名为 global（形参）
            // 在代码风格上对全局对象的引用变得比引用一个没有“全局” 字样的变量更加清晰
            var a = 2;
            (function IIFE( global ) {
                var a = 3;
                console.log( a ); // 3
                console.log( global.a ); // 2
            })( window );
            console.log( a ); // 2
            </code>
        </pre>
    </dd>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                // 解决 undefined 标识符的默认值被错误覆盖导致的异常
                undefined = true; // 给其他代码挖了一个大坑！ 绝对不要这样做！
                (function IIFE( undefined ) {
                    var a;
                if (a === undefined) {
                    console.log( "Undefined is safe here!" );
                }
                })();
            </code>
        </pre>
    </dd>
    <dt>try/catch</dt>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                try {
                    undefined(); // 执行一个非法操作来强制制造一个异常
                }
                catch (err) {
                    console.log( err ); // 能够正常执行！
                } 
                console.log( err ); // ReferenceError: err not found
            </code>
        </pre>
    </dd>
</dl>

<h2>作用域闭包</h2>
<dl>
    <dt>闭包: 函数是在当前词法作用域之外执行</dt>
    <dd>是基于词法作用域书写代码时所产生的自然结果， 你甚至不需要为了利用它们而有意
        识地创建闭包。</dd>
    <dd>当函数可以记住并访问所在的词法作用域时， 就产生了闭包， 即使函数是在当前词法作用
        域之外执行。</dd>
    <dt>词法作用域</dt>
        <dd>函数 bar() 的词法作用域能够访问 foo() 的内部作用域。 </dd>
    <dt>内函数在词法作用域以外的地方执行</dt>
    <dd>然后我们将 bar() 函数本身当作 一个值类型进行传递，在自己定义的词法作用域以外的地方
        执行</dd>
    <dd>
        在 foo() 执行后， <b>通常</b>会期待 foo() 的整个内部作用域都被销毁， 因为我们知道引擎有垃
圾回收器用来释放不再使用的内存空间。 
    </dd>
    <dd>bar() 依然持有对该作用域的引用， 而这个引用就叫作<b>闭包</b>。</dd>
    <dd>无论通过何种手段将<b>内部函数传递到所在的词法作用域以外</b>， 它都会持有对原始定义作用
        域的引用， 无论在何处执行这个函数都会使用闭包</dd>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                function foo() {
                    var a = 2;
                    function bar() {
                    console.log( a );
                }
                return bar;
                }
                var baz = foo();
                baz(); // 2 —— 朋友， 这就是闭包的效果。
            </code>
        </pre>
        <dt>块作用域和闭包联手便可天下无敌</dt>
        <pre class="language-javascript line-number">
            <code>
                // 延迟函数的回调会在循环结束时才执行。 
                // 事实上，当定时器运行时即使每个迭代中执行的是 setTimeout(.., 0)， 
                // 所有的回调函数依然是在循环结束后才会被执行
                for (var i=1; i<=5; i++) {
                    // 在迭代内使用 IIFE 会为每个迭代都生成一个新的作用域， 
                    // 使得延迟函数的回调可以将新的作用域封闭在每个迭代内部，
                    // 每个迭代中都会含有一个具有正确值的变量供我们访问。
                    (function(j) {
                        setTimeout( function timer() {
                            console.log( j );
                        }, j*1000 );
                    })( i );
                }
            </code>
        </pre>
        <pre class="language-javascript line-number">
            <code>
                // for 循环头部的 let 声明指出变量在循环过程中不止被声明一次， 每次迭代都会声明。 
                // 随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量
                for (let i=1; i<=5; i++) {
                    setTimeout( function timer() {
                        console.log( i );
                    }, i*1000 );
                }
            </code>
        </pre>
    </dd>
</dl>
<h2>动态作用域</h2>
<dl>
    <dt>词法作用域和动态作用域的主要区别</dt>
    <blockquote cite="https://www.jianshu.com/p/70b38c7ab69c">
        <a href="https://www.jianshu.com/p/70b38c7ab69c">事实上, Javascript并不具有动态作用域，它只有词法作用域，简单明了。
            但是，它的 eval()、with、this机制某种程度上很像动态作用域，使用上要特别注意。</a>
    </blockquote>
    <dd>词法作用域是在写代码或者说定义时确定的，</dd>
    <dd> 而动态作用域是在运行时确定的。（this 也是！ ） </dd>
    <dd>词法作用域关注函数在何处声明， 而动态作用域关注函数从何处调用。</dd>
    <dd>this 关注函数如何调用， 这就表明了 this 机制和动态作用域之间的关系多么紧密</dd>
</dl>

<h1>this和对象原型</h1>
<h2>this</h2>
<p>this关键字， 被自动定义在所有函数的作用域中</p>
<pre class="language-javascript line-number">
    <code>
        function identify() {
            return this.name.toUpperCase();
        }

        function speak() {
            var greeting = "Hello, I'm " + identify.call( this );
            console.log( greeting );
        }
            var me = {
            name: "Kyle"
        };
            var you = {
            name: "Reader"
        };

        identify.call( me ); // KYLE
        identify.call( you ); // READER

        speak.call( me ); // Hello, 我是 KYLE
        speak.call( you ); // Hello, 我是 READER

        // 这段代码可以在不同的上下文对象（me 和 you） 中重复使用函数 identify() 和 speak()，
        // 不用针对每个对象编写不同版本的函数。
        
        // 如果不使用 this， 那就需要给 identify() 和 speak() 显式传入一个上下文对象。
        function identify(context) {
            return context.name.toUpperCase();
        }
        function speak(context) {
            var greeting = "Hello, I'm " + identify( context );
            console.log( greeting );
        }
        
        identify( you ); // READER
        speak( me ); //hello, 我是 KYLE
    </code>
</pre>
<h3>this 误解</h3>
<dl>
    <dt>指向自身</dt>
    <dd> this 并不像我们所想的那样指向函数本身</dd>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                function foo(num) {
                    console.log( "foo: " + num );
                    // 记录 foo 被调用的次数
                    this.count++;
                } 
                // 向函数对象 foo 添加一个属性 count
                foo.count = 0;
                var i;
                for (i=0; i<10; i++) {
                    if (i > 5) {
                    foo( i );
                    }
                } 
                // foo: 6
                // foo: 7
                // foo: 8
                // foo: 9
                // foo 被调用了多少次？
                console.log( foo.count ); // 0 -- WTF?
                // 函数内部代码this.count 中的 this 并不是指向那个函数对象， 
                // 所以虽然属性名相同， 根对象却并不相同
            </code>
        </pre>
    </dd>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                // 回避这个问题并使用其他方法来达到目的， 比如创建另一个带有 count 属性的对象
                function foo(num) {
                    console.log( "foo: " + num );
                    // 记录 foo 被调用的次数
                    data.count++;
                }
                var data = {
                    count: 0
                };
                var i;
                for (i=0; i<10; i++) {
                    if (i > 5) {
                    foo( i );
                    }
                } 
                // foo: 6
                // foo: 7
                // foo: 8
                // foo: 9
                // foo 被调用了多少次？
                console.log( data.count ); // 4
            </code>
        </pre>
    </dd>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                // 从函数对象内部引用它自身， 那只使用 this 是不够的
                // 需要通过一个指向函数对象的词法标识符（变量） 来引用它
                function foo(num) {
                    console.log( "foo: " + num );
                    // 记录 foo 被调用的次数
                    foo.count++;
                }
                
                foo.count=0
                var i;
                for (i=0; i<10; i++) {
                    if (i > 5) {
                        foo( i );
                    }
                }

                // foo: 6
                // foo: 7
                // foo: 8
                // foo: 9
                // foo 被调用了多少次？
                console.log( foo.count ); // 4

                setTimeout( function(){
                    // 匿名（没有名字的） 函数无法指向自身
                }, 10 );
            </code>
        </pre>
    </dd>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                // 强制 this 指向 foo 函数对象
                function foo(num) {
                    console.log( "foo: " + num );
                    // 记录 foo 被调用的次数
                    // 注意， 在当前的调用方式下（参见下方代码）， this 确实指向 foo
                    this.count++;
                } 

                foo.count = 0;
                var i;
                for (i=0; i<10; i++) {
                    if (i > 5) {
                        // 使用 call(..) 可以确保 this 指向函数对象 foo 本身
                        foo.call( foo, i );
                    }
                } 
                // foo: 6
                // foo: 7
                // foo: 8
                // foo: 9
                // foo 被调用了多少次？
                console.log( foo.count ); // 4
            </code>
        </pre>
    </dd>
    <dt>this 指向函数的作用域</dt>
    <dd>某种情况下它是正确的， 但是在其他情况下它却是错误的</dd>
    <dd>需要明确的是， this 在任何情况下都不指向函数的词法作用域</dd>
    <dd>在 JavaScript 内部， 作用域确实和对象类似， 可见的标识符都是它的属性</dd>
    <dd>但是作用域“对象” 无法通过 JavaScript代码访问， 它存在于 JavaScript 引擎内部</dd>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                // 每当你想要把 this 和词法作用域的查找混合使用时， 一定要提醒自己， 这是无法实现的
                function foo() {
                    var a = 2;
                    this.bar();
                }
                function bar() {
                    console.log( this.a );
                } 

                foo(); // ReferenceError: a is not defined
            </code>
        </pre>
    </dd>
    <dt>this到底是什么</dt>
    <dd>
        this 是在运行时进行绑定的， 并不是在编写时绑定， 它的上下文取决于函数调用时的各种条件。 
    </dd>
    <dd>this 的绑定和函数声明的位置没有任何关系， 只取决于函数的调用方式。</dd>
    <dd>this 实际上是在函数被调用时发生的绑定， 它指向什么完全取决于函数在哪里被调用。</dd>
</dl>
<h3>this全面解析</h3>
<dl>
    <dt>调用位置</dt>
    <dd>调用位置就是函数在代码中被调用的位置（而不是声明的位置）。</dd>
    <dd>只有仔细分析调用位置才能回答： 这个 this 到底引用的是什么？</dd>
    <dd>最重要的是要分析调用栈（我们关心的调用位置就在当前正在执行的函数的前一个调用中）。</dd>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                // 调用栈和调用位置
                // 如何（从调用栈中） 分析出真正的调用位置的， 因为它决定了 this 的绑定
                function baz() {
                    // 当前调用栈是： baz
                    // 因此， 当前调用位置是全局作用域
                    console.log( "baz" );
                    bar(); // <-- bar 的调用位置
                }
                function bar() {
                    // 当前调用栈是 baz -> bar
                    // 因此， 当前调用位置在 baz 中
                    console.log( "bar" );
                    foo(); // <-- foo 的调用位置
                }
                function foo() {
                    // 当前调用栈是 baz -> bar -> foo
                    // 因此， 当前调用位置在 bar 中
                    console.log( "foo" );
                }
                
                baz(); // <-- baz 的调用位置
            </code>
        </pre>
    </dd>
</dl>
<h4>四条绑定规则</h4>
<dl>
    <dt>默认绑定</dt>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                function foo() {
                    console.log( this.a );
                }
                var a = 2;
                // 对象 ： 命名空间
                // 调用栈 全局对象 --> foo对象
                // 调用位置（在全局作用域中）
                // 本例中， 函数调用时应用了 this 的默认绑定， 因此 this 指向全局对象
                foo(); // 2  

                // 在代码中， foo() 是直接使用不带任何修饰的函数引用进行调用的， 
                // 因此只能使用默认绑定， 无法应用其他规则。

                // 如果使用严格模式（strict mode）， 那么全局对象将无法使用默认绑定， 
                // 因此 this 会绑定到 undefined：

                function foo() {
                    // 严格模式下与 foo()的调用位置无关：
                    "use strict";
                    console.log( this.a );
                }
                var a = 2;
                foo(); // TypeError: this is undefined
            </code>
        </pre>
    </dd>
    <dt>隐式绑定</dt>
    <dd>在一个对象内部包含一个指向函数的属性， 并通过这个属性间接引用函数， 从而把 this 间接（隐式） 绑定到这个对象上</dd>
    <dd>调用位置是否有上下文对象， 或者说是否被某个对象拥有或者包含</dd>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                //  foo() 的声明
                function foo() {
                    console.log( this.a );
                }
                var obj = {
                    a: 2,
                    foo: foo //  foo被当作引用属性添加到 obj 中
                };
                // 无论是直接在 obj 中定义还是先定义再添加为引用属性， 
                // foo函数严格来说都不属于obj 对象

                // 调用位置会使用 obj 上下文来引用函数， 
                // 因此你可以说函数被调用时 obj 对象“拥有” 或者“包含” foo
                obj.foo(); // 2
                // 当函数引用有上下文对象时， 隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象
            </code>
        </pre>
    </dd>
    <dd>隐式丢失(不带任何修饰的函数调用)</dd>
    <dd>最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象</dd>
    <dd>也就是说它会应用默认绑定， 从而把 this 绑定到全局对象或者 undefined 上， 取决于是否是严格模式</dd>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                function foo() {
                    console.log( this.a );
                }
                var obj = {
                    a: 2,
                    foo: foo
                };
                var bar = obj.foo; // 函数别名！
                var a = "oops, global"; // a 是全局对象的属性
                bar(); // "oops, global"
                //  bar 是 obj.foo 的一个引用
                // 它引用的是 foo 函数本身， 因此此时的bar() 其实是一个不带任何修饰的函数调用， 因此应用了默认绑定
                
                // 一种更微妙、 更常见并且更出乎意料的情况发生在传入回调函数时：
                function foo() {
                    console.log( this.a );
                }
                function doFoo(fn) {
                    // fn 其实引用的是 foo
                    fn(); // <-- 调用位置！
                }
                var obj = {
                    a: 2,
                    foo: foo
                };
                var a = "oops, global"; // a 是全局对象的属性
                doFoo( obj.foo ); // "oops, global
                // 参数传递其实就是一种隐式赋值， 因此我们传入函数时也会被隐式赋值， 
                // 所以结果和上一 个例子一样
            </code>
        </pre>
    </dd>
</dl>
<dl>
    <dt>显式绑定</dt>
    <dd>不想在对象内部包含函数引用， 而想在某个对象上强制调用函数</dd>
    <dd>可以使用函数的 call(..) 和apply(..) 方法。JavaScript 中的“所有” 函数都有一些有用的特性</dd>
    <dd>call(..) 和 apply(..) 方法的第一个参数是一个对象， 它们会把这个对象绑定到
        this， 接着在调用函数时指定这个 this</dd>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                function foo() {
                    console.log( this.a );
                }
                var obj = {
                    a:2
                };
                // 通过 foo.call(..)， 我们可以在调用 foo 时强制把它的 this 绑定到 obj 上
                foo.call( obj ); // 2

            </code>
        </pre>
    </dd>
    <dd>显式绑定仍然无法解决我们之前提出的丢失绑定问题。但是显式绑定的一个变种可以解决这个问题</dd>
    <dd>
        <ul>
            <li>硬绑定</li>
            <li>
                <pre class="language-javascript line-number">
                    <code>
                        function foo() {
                            console.log( this.a );
                        }
                        var obj = {
                            a:2
                        };
                        "创建了函数 bar()， 并在它的内部手动调用了 foo.call(obj)， 因此强制把 foo 的 this 绑定到了 obj。 
                        无论之后如何调用函数 bar， 它总会手动在 obj 上调用 foo。 这种绑定是一种显式的强制绑定， 因此我们称之为硬绑定。"
                        var bar = function() {
                            foo.call( obj );
                        };
                        bar(); // 2
                        setTimeout( bar, 100 ); // 2
                        // 硬绑定的 bar 不可能再修改它的 this
                        bar.call( window ); // 2
                        
                    </code>
                </pre>
            </li>
            <li>
                <pre class="language-javascript line-number">
                    <code>
                        // 另一种使用方法是创建一个可以重复使用的辅助函数：
                        function foo(something) {
                            console.log( this.a, something );
                            return this.a + something;
                        } /
                        / 简单的辅助绑定函数
                        function bind(fn, obj) {
                            return function() {
                                return fn.apply( obj, arguments );
                        };
                        }
                        var obj = {
                            a:2
                        };
                        var bar = bind( foo, obj );
                        var b = bar( 3 ); // 2 3
                        console.log( b ); // 5
                    </code>
                </pre>
            </li>
            <li>API调用的“上下文”</li>
            <li>
                <pre class="language-javascript line-number">
                    <code>
                        function foo(el) {
                            console.log( el, this.id );
                        }
                        var obj = {
                            id: "awesome"
                        };
                        // 调用 foo(..) 时把 this 绑定到 obj
                        // 这些函数实际上就是通过 call(..) 或者 apply(..) 实现了显式绑定
                        [1, 2, 3].forEach( foo, obj );
                        // 1 awesome 2 awesome 3 awesome
                    </code>
                </pre>
            </li>
        </ul>
    </dd>
</dl>

<dl>
    <dt>new绑定</dt>
    <dd>使用 new 来调用函数， 或者说发生构造函数调用时， 会自动执行下面的操作。</dd>
    <dd>
        <ol>
            <li>创建（或者说构造） 一个全新的对象</li>
            <li>这个新对象会被执行 [[ 原型 ]] 连接</li>
            <li>这个新对象会绑定到函数调用的 this</li>
            <li>如果函数没有返回其他对象， 那么 new 表达式中的函数调用会自动返回这个新对象</li>
        </ol>
    </dd>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                // 使用 new 来调用 foo(..) 时， 我们会构造一个新对象并把它绑定到 foo(..) 调用中的 this上。 
                function foo(a) {
                    this.a = a;
                }
                var bar = new foo(2);
                console.log( bar.a ); // 2
            </code>
        </pre>
    </dd>
</dl>
<h4>优先级</h4>

<dl>
    <dt>判断this</dt>
    <dd>根据优先级来判断函数在某个调用位置应用的是哪条规则</dd>
    <dd>
        <ol>
            <li>函数是否在 new 中调用（new 绑定） ？ 如果是的话 this 绑定的是新创建的对象。</li>
            <pre class="language-javascript line-number">
                <code>
                    var bar = new foo()
                </code>
            </pre>
            <li>函数是否通过 call、 apply（显式绑定） 或者硬绑定调用？ 如果是的话， this 绑定的是
                指定的对象</li>
            <pre class="language-javascript line-number">
                <code>
                    var bar = foo.call(obj2)
                </code>
            </pre>
            <li>函数是否在某个上下文对象中调用（隐式绑定） ？ 如果是的话， this 绑定的是那个上
                下文对象</li>
            <pre class="language-javascript line-number">
                <code>
                    var bar = obj1.foo()
                </code>
            </pre>
            <li>如果都不是的话， 使用默认绑定。 如果在严格模式下， 就绑定到 undefined， 否则绑定到
                全局对象</li>
            <pre class="language-javascript line-number">
                <code>
                    var bar = foo()
                </code>
            </pre>
        </ol>
    </dd>
    <dt>绑定例外</dt>
    <dd>在某些场景下 this 的绑定行为会出乎意料， 你认为应当应用其他绑定规则时， 实际上应用
        的可能是默认绑定规则。</dd>
    <dd>
        <ol>
            <li>被忽略的this</li>
            <pre class="language-javascript line-number">
                <code>
                    // 如果你把 null 或者 undefined 作为 this 的绑定对象传入 call、 apply 或者 bind， 
                    // 这些值在调用时会被忽略， 实际应用的是默认绑定规则：
                    function foo() {
                        console.log( this.a );
                    }
                    var a = 2;
                    foo.call( null ); // 2

                    //  用 apply(..) 来“展开” 一个数组， 并当作参数传入一个函数
                    function foo(a,b) {
                        console.log( "a:" + a + ", b:" + b );
                    } 
                    / 把数组“展开” 成参数
                    foo.apply( null, [2, 3] ); // a:2, b:3
                    // 使用bind(..) 可以对参数进行柯里化（预先设置一些参数）
                    var bar = foo.bind( null, 2 );
                    bar( 3 ); // a:2, b:3
                    // 这两种方法都需要传入一个参数当作 this 的绑定对象。 
                    // 如果函数并不关心 this 的话， 你仍然需要传入一个占位值， 这时 null 可能是一个不错的选择， 就像代码所示的那样
                </code>
            </pre>
            <li>更安全的this</li>
            <pre class="language-javascript line-number">
                <code>
                    // 传入一个特殊的对象， 把 this 绑定到这个对象不会对你的程序产生任何副作用
                    function foo(a,b) {
                        console.log( "a:" + a + ", b:" + b );
                    } 
                    // 我们的 DMZ 空对象(可以用任何喜欢的名字来命名 DMZ 对象-- demilitarized zone， 非军事区)
                    var ø = Object.create( null );
                    // 把数组展开成参数
                    foo.apply( ø, [2, 3] ); // a:2, b:3
                    // 使用 bind(..) 进行柯里化
                    var bar = foo.bind( ø, 2 );
                    bar( 3 ); // a:2, b:3

                    // 用变量名 ø 不仅让函数变得更加“安全”， 而且可以提高代码的可读性， 
                    // 因为 ø 表示“我希望 this 是空”， 这比 null 的含义更清楚。
                </code>
            </pre>
            <li>间接引用</li>
            <pre class="language-javascript line-number">
                <code>
                    // 一个函数的“间接引用”， 在这种情况下， 调用这个函数会应用默认绑定规则
                    // 间接引用最容易在赋值时发生
                    function foo() {
                        console.log( this.a );
                    }
                    var a = 2;
                    var o = { a: 3, foo: foo };
                    var p = { a: 4 };
                    o.foo(); // 3
                    (p.foo = o.foo)(); // 2

                    // 赋值表达式 p.foo = o.foo 的返回值是目标函数的引用， 
                    // 因此调用位置是 foo() 而不是p.foo() 或者 o.foo()

                    // 对于默认绑定来说， 决定 this 绑定对象的并不是调用位置是否处于严格模式， 而是函数体是否处于严格模式。 
                    // 如果函数体处于严格模式， this 会被绑定到 undefined， 否则this 会被绑定到全局对象
                </code>
            </pre>
            <li>软绑定</li>
            <pre class="language-javascript line-number">
                <code>
                    // 硬绑定这种方式可以把 this 强制绑定到指定的对象（除了使用 new 时）， 防止函数调用应用默认绑定规则
                    // 问题在于， 硬绑定会大大降低函数的灵活性， 使用硬绑定之后就无法使用隐式绑定或者显式绑定来修改 this
                
                    // 如果可以给默认绑定指定一个全局对象和 undefined 以外的值， 
                    // 那就可以实现和硬绑定相同的效果， 同时保留隐式绑定或者显式绑定修改 this 的能力
                    if (!Function.prototype.softBind) {
                        Function.prototype.softBind = function(obj) {
                            var fn = this;
                            // 捕获所有 curried 参数
                            var curried = [].slice.call( arguments, 1 );
                            var bound = function() {
                                return fn.apply(
                                    (!this || this === (window || global)) ?
                                        obj : this
                                    curried.concat.apply( curried, arguments )
                                );
                            };
                        bound.prototype = Object.create( fn.prototype );
                        return bound;
                        };
                    }
                </code>
            </pre>
            <pre class="language-javascript line-number">
                <code>
                    function foo() {
                        console.log("name: " + this.name);
                    }
                    var obj = { name: "obj" },
                        obj2 = { name: "obj2" },
                        obj3 = { name: "obj3" };
                    var fooOBJ = foo.softBind( obj );
                    
                    fooOBJ(); // name: obj
                    
                    obj2.foo = foo.softBind(obj);
                    obj2.foo(); // name: obj2 <---- 看！ ！ ！
                    
                    fooOBJ.call( obj3 ); // name: obj3 <---- 看！
                    
                    setTimeout( obj2.foo, 10 );
                    // name: obj <---- 应用了软绑定

                    // 软绑定版本的 foo() 可以手动将 this 绑定到 obj2 或者 obj3 上， 
                    // 但如果应用默认绑定， 则会将 this 绑定到 obj
                </code>
            </pre>
        </ol>
    </dd>
</dl>
<h4>this词法</h4>
<p>四条规则已经可以包含所有正常的函数</p>
<p>但是 ES6 中介绍了一种无法使用这些规则的特殊函数类型： 箭头函数</p>
<p>箭头函数不使用 this 的四种标准规则， 而是根据外层（函数或者全局） 作用域来决定 this</p>
<dl>
    <dt></dt>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                function foo() {
                    // 返回一个箭头函数
                    return (a) => {
                        //this 继承自 foo()
                        console.log( this.a );
                    };
                }
                var obj1 = {
                    a:2
                };
                var obj2 = {
                    a:3
                };
                var bar = foo.call( obj1 );
                bar.call( obj2 ); // 2, 不是 3 ！

                // foo() 内部创建的箭头函数会捕获调用时 foo() 的 this。
                // 由于 foo() 的 this 绑定到 obj1， bar（ 引用箭头函数） 的 this 也会绑定到 obj1， 箭头函数的绑定无法被修改。（new 也不行！ ）

                // ES6 中的箭头函数并不会使用四条标准的绑定规则， 而是根据当前的词法作用域来决定 this
                // 具体来说， 箭头函数会继承外层函数调用的 this 绑定（无论 this 绑定到什么）
            </code>
        </pre>
    </dd>
    <dd></dd>
</dl>

<h2>对象</h2>
<h3>语法</h3>
<p>对象可以通过两种形式定义： 声明（文字） 形式和构造形式</p>
<pre class="language-javascript line-number">
    <code>
        // 构造形式和文字形式生成的对象是一样的
        // 唯一的区别是， 在文字声明中你可以添加多个 键 / 值对， 但是在构造形式中你必须逐个添加属性

        // 对象的文字语法大概是这样：
        var myObj = {
            key: value
            // ...
        };
        // 构造形式大概是这样：
        var myObj = new Object();
        myObj.key = value;
    </code>
</pre>
<h3>类型</h3>
<p>对象是 JavaScript 的基础。 在 JavaScript 中一共有六种主要类型（术语是“语言类型”） ：</p>
<ol>
    <li>string</li>
    <li>number</li>
    <li>boolean</li>
    <li>null</li>
    <li>undefined</li>
    <li>object</li>
</ol>
<p>简单基本类型（string、 boolean、 number、 null 和 undefined） 本身并不是对象</p>
<p>null 有时会被当作一种对象类型， 但是这其实只是语言本身的一个 bug， 即对 null 执行
    typeof null 时会返回字符串 "object"。 1 实际上， null 本身是基本类型。</p>
<p><b>有一种常见的错误说法是“JavaScript 中万物皆是对象”， 这显然是错误的</b></p>
<p>内置对象</p>
<p>JavaScript 中还有一些对象子类型， 通常被称为内置对象</p>
<ol>
    <li>String</li>
    <li>Number</li>
    <li>Boolean</li>
    <li>Object</li>
    <li>Function</li>
    <li>Array</li>
    <li>Date</li>
    <li>RegExp</li>
    <li>Error</li>
    <p>实际上只是一些内置函数。 这些内置函数可以当作构造函数来使用， 从而可以构造一个对应子类型的新对象</p>
    <pre class="language-javascript line-number">
        <code>
            // 原始值 "I am a string" 并不是一个对象， 它只是一个字面量， 并且是一个不可变的值
            // 如果要在这个字面量上执行一些操作， 比如获取长度、 访问其中某个字符等， 那需要将其转换为 String 对象
            // 必要时语言会自动把字符串字面量转换成一个 String 对象, 你并不需要显式创建一个对象
            var strPrimitive = "I am a string";
            typeof strPrimitive; // "string"
            strPrimitive instanceof String; // false

            var strObject = new String( "I am a string" );
            typeof strObject; // "object"
            strObject instanceof String; // true

            // 检查 sub-type 对象
            Object.prototype.toString.call( strObject ); // [object String]
        </code>
    </pre>
</ol>

<h3>内容-属性</h3>
<pre class="language-javascript line-number">
    <code>
        var myObject = {
            a: 2
        };
        myObject.a; // 2
        myObject["a"]; // 2

        // .a 语法通常被称为“属性访问”，
        // ["a"] 语法通常被称为“键访问”
        // 实际上它们访问的是同一个位置
        // 主要区别在于 . 操作符要求属性名满足标识符的命名规范， 
        // 而 [".."] 语法可以接受任意 UTF-8/Unicode 字符串作为属性名
    </code>
</pre>
<h3>数组</h3>

<pre class="language-javascript line-number">
    <code>
        var myArray = [ "foo", 42, "bar" ];
        myArray.length; // 3
        myArray[0]; // "foo"
        myArray[2]; // "bar"
        // 数组也是对象， 所以虽然每个下标都是整数， 你仍然可以给数组添加属性：
        var myArray = [ "foo", 42, "bar" ];
        myArray.baz = "baz";
        myArray.length; // 3
        myArray.baz; // "baz"
        // 可以看到虽然添加了命名属性（无论是通过 . 语法还是 [] 语法）， 数组的 length 值并未发生变化。

        // 如果你试图向数组添加一个属性， 但是属性名“看起来” 像一个数字， 
        // 那它会变成一个数值下标（因此会修改数组的内容而不是添加一个属性） 
        var myArray = [ "foo", 42, "bar" ];
        myArray["3"] = "baz";
        myArray.length; // 4
        myArray[3]; // "baz"
    </code>
</pre>

<h2>混合对象 “类”</h2>

<p>类 / 继承描述了一种代码的组织结构形式——一种在软件中对真实世界中问题领域的建模方法</p>
<p>面向对象编程强调的是<b>数据</b>和<b>操作数据的行为</b>本质上是互相关联的</p>
<p>好的设计就是把<b>数据以及和它相关的行为打包</b>（或者说封装） 起来</p>
<p>这在正式的计算机科学中有时被称为<b>数据结构</b></p>
<p>字符就是数据。 但是你<b>关心的</b>往往不是数据是什么， 而<b>是可以对数据做什么</b>， 所以<b>可以应用在这种数据上的
    行为（计算长度、 添加数据、 搜索， 等等） 都被设计成 String 类的方法</b>。</p>
<p>所有字符串都是 String 类的一个实例， 也就是说它是一个包裹， 包含字符数据和我们可以
    应用在数据上的函数。</p>



<h2>原型</h2>

<blockquote>对象（和函数！ 别忘了函数也
    是对象） 只能复制引用， 无法复制被引用的对象或者函数本身。 忽视这一点会导致许多
    问题</blockquote>

<p>JavaScript 中的对象有一个特殊的 [[Prototype]] 内置属性， 其实就是对于其他对象的引
    用</p>
<p>几乎所有的对象在创建时 [[Prototype]] 属性都会被赋予一个非空的值</p>
<pre class="language-javascript line-number">
    <code>
        var myObject = {
            a:2
        };
        myObject.a; // 2
    </code>
</pre>
<p>当你试图引用对象的属性时会触发[[Get]] 操作， 比如 myObject.a。 对于默认的 [[Get]] 操作来说， 第一步是检查对象本身是
    否有这个属性， 如果有的话就使用它。</p>
<p>但是如果 a 不在 myObject 中， 就需要使用对象的 [[Prototype]] 链了。</p>

<p>对于默认的 [[Get]] 操作来说， 如果无法在对象本身找到需要的属性， 就会继续访问对象
    的 [[Prototype]] 链：</p>
<pre class="language-javascript line-number">
    <code>
        var anotherObject = {
            a:2
        };
        // 创建一个关联到 anotherObject 的对象
        var myObject = Object.create( anotherObject );
        myObject.a; // 2

        // Object.create(..) 会创建一个对象并把这个对象的 [[Prototype]] 关联到指定的对象。
    </code>
</pre>

<pre class="language-javascript line-number">
    <code>
        function NothingSpecial() {
            console.log( "Don't mind me!" );
        }
        var a = new NothingSpecial();
        // "Don't mind me!"
        a; // {}
    </code>
</pre>

<blockquote>
    在 JavaScript 中对于“构造函数” 最准确的解释是， 所有带 new 的函数调用。
函数不是构造函数， 但是当且仅当使用 new 时， 函数调用会变成“构造函数调用”。
</blockquote>

<pre class="language-javascript line-number">
    <code>
        var anotherObject = {
            cool: function() {
                console.log( "cool!" );
            }
        };
        var myObject = Object.create( anotherObject );
        myObject.doCool = function() {
            this.cool(); // 内部委托！
        };
        myObject.doCool(); // "cool!"

        // 调用的 myObject.doCool() 是实际存在于 myObject 中的， 
        // 这可以让我们的 API 设计更加清晰（不那么“神奇”）

        // 内部委托比起直接委托可以让 API 接口设计更加清晰
    </code>
</pre>

<h2>行为委托</h2>

<blockquote>[[Prototype]] 机制就是指对象中的一个内部链接引用
    另一个对象</blockquote>

<p> 执行任务“XYZ” 需要两个兄弟对象（XYZ 和 Task） 协作完成。 
    但是我们并不需要把这些行为放在一起， 通过类的复制， 我们可以把它们分别放在各自独立
    的对象中， 需要时可以允许 XYZ 对象委托给 Task。</p>
<pre class="language-javascript line-number">
    <code>
        Task = {
            setID: function(ID) { this.id = ID; },
            outputID: function() { console.log( this.id ); }
        };
        // 让 XYZ 委托(使用) Task
        XYZ = Object.create( Task );

         // Task 和 XYZ 并 不 是 类（ 或 者 函 数 ）， 它 们 是 对 象。 
         // XYZ 通 过 Object.create(..) 创建， 它的 [[Prototype]] 委托了 Task 对象


        XYZ.prepareTask = function(ID,Label) {
            this.setID( ID );
            this.label = Label;
        };

        XYZ.outputTaskDetails = function() {
            this.outputID();
            console.log( this.label );
        };

        // ABC = Object.create( Task );
        // ABC ... = ...
    </code>
</pre>

<h3>比较思维模型</h3>
<dl>
    <dt>面向对象风格</dt>
    <dd><pre class="language-javascript line-number">
        <code>
            function Foo(who) {
                this.me = who;
            }
            Foo.prototype.identify = function() {
                return "I am " + this.me;
            };
            
            function Bar(who) {
                Foo.call( this, who );
            }
            // Bar.prototype 委托了 Foo.prototype
            // 子类 Bar 继承了父类 Foo
            Bar.prototype = Object.create( Foo.prototype );
            Bar.prototype.speak = function() {
                alert( "Hello, " + this.identify() + "." );
            };
            // 子类 Bar 生成了 b1 和 b2 两个实例
            // b1 委托了 Bar.prototype
            var b1 = new Bar( "b1" );
            var b2 = new Bar( "b2" );
            b1.speak();
            b2.speak();
        </code>
    </pre>
    </dd>
    <dd>
        <img src="../static/images/类风格代码的思维模型强调实体以及实体间的关系.png" alt='类风格代码的思维模型强调实体以及实体间的关系' title='类风格代码的思维模型强调实体以及实体间的关系'>
    </dd>
    <dt>对象关联风格</dt>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                // 和上一段代码一模一样, 仍然实现了三个对象之间的关联
                Foo = {
                            init: function(who) {
                                this.me = who;
                            },
                            identify: function() {
                                return "I am " + this.me;
                            }
                        };

                // 把 Bar 委托给 Foo
                Bar = Object.create( Foo );
                Bar.speak = function() {
                    alert( "Hello, " + this.identify() + "." );
                };

                // 同样利用 [[Prototype]] 把 b1 委托给 Bar
                var b1 = Object.create( Bar );
                b1.init( "b1" );

                var b2 = Object.create( Bar );
                b2.init( "b2" );

                b1.speak();
                b2.speak();

                // 非常重要的一点是， 这段代码简洁了许多，
                // 我们只是把对象关联起来， 并不需要那些既复杂又令人困惑的模仿类的行为（构造函数、 原型以及 new）。
            </code>
        </pre>
    </dd>
    <dd>
        <img src="../static/images/对象关联风格的代码只关注对象之间的关联关系.png" alt="对象关联风格的代码只关注对象之间的关联关系" title="对象关联风格的代码只关注对象之间的关联关系">
    </dd>
</dl>


<blockquote>
    行为委托认为对象之间是兄弟关系， 互相委托， 而不是父类和子类的关系。 <br>
    JavaScript 的[[Prototype]] 机制本质上就是行为委托机制。
</blockquote>
<p>传统面向类的语言中父类和子类、 子类和实例之间其实是复制操作，<br>
    但是在 [[Prototype]] 中并没有复制， 相反， 它们之间只有委托关联。
</p>

<p>class 很好地伪装成 JavaScript 中类和继承设计模式的解决方案， 但是它实际上起到了反作
    用： 它隐藏了许多问题并且带来了更多更细小但是危险的问题。</p>
<p>class 加深了过去 20 年中对于 JavaScript 中“类” 的误解， 在某些方面， 它产生的问题比
    解决的多， 而且让本来优雅简洁的 [[Prototype]] 机制变得非常别扭。</p>
    <p>结论： 如果 ES6 的 class 让 [[Prototype]] 变得更加难用而且隐藏了 JavaScript 对象最重要
        的机制——对象之间的实时委托关联， 我们难道不应该认为 class 产生的问题比解决的多
        吗？ 难道不应该抵制这种设计模式吗？</p>



<h1>类型和语法</h1>
<p>ECMAScript 类型又进一步细分为语言类型和规范类型</p>
<p>ECMAScript 语言中所有的值都有一个对应的语言类型。 </p>
<p>ECMAScript 语言类型包括 Undefined、 Null、 Boolean、 String、 Number 和 Object。</p>

<blockquote>
    “类型”（与规范类似）：对语言引擎和开发人员来说， 类型是值
的内部特征，它定义了值的行为，以使其区别于其他值。
</blockquote>

<h2>内置类型</h2>
<p>JavaScript 有七种内置类型：</p>
<ol>
    <li>空值（null）</li>
    <li>未定义（undefined）</li>
    <li>布尔值（ boolean）</li>
    <li>数字（number）</li>
    <li>字符串（string）</li>
    <li>对象（object）</li>
    <li>符号（symbol， ES6 中新增）</li>
</ol>
<blockquote>
    除对象之外，其他统称为“基本类型”。
    <br>
    typeof 运算符来查看值的类型，它返回的是类型的字符串值
</blockquote>

<pre class="language-javascript line-number">
    <code>
        typeof undefined === "undefined"; // true
        typeof true === "boolean"; // true
        typeof 42 === "number"; // true
        typeof "42" === "string"; // true
        typeof { life: 42 } === "object"; // true
        // ES6中新加入的类型
        typeof Symbol() === "symbol"; // true

        // 以上六种类型均有同名的字符串值与之对应

        // null 类型不在此列。它比较特殊， typeof 对它的处理有问题：
        typeof null === "object"; // true

        // 需要使用复合条件来检测 null 值的类型：
        var a = null;
        (!a && typeof a === "object"); // true
    </code>
</pre>
<p>null 是基本类型中唯一的一个“假值”（falsy 或者 false-like）类型， typeof 对它的返回值为 "object"。</p>

<pre class="language-javascript line-number">
    <code>
        typeof function a(){ /* .. */ } === "function"; // true

        // 这样看来， function（函数）也是 JavaScript 的一个内置类型。
        // 然而查阅规范就会知道，它实际上是 object 的一个“子类型”。
        // 具体来说，函数是“可调用对象”，它有一个内部属性 [[Call]]，该属性使其可以被调用。

        // 函数不仅是对象，还可以拥有属性
        function a(b,c) {
            /* .. */
        }
        
        // 函数对象的 length 属性是其声明的参数的个数：
        a.length; // 2
        
        typeof [1,2,3] === "object"; // true
        // 数组也是对象，它也是 object 的一个“子类型”，数组的元素按数字顺序来进行索引
        // 其 length 属性是元素的个数
    </code>
</pre>

<blockquote>
    JavaScript 中的<b>变量是没有类型</b>的， 只有值才有。变量可以随时持有任何类型的值。
</blockquote>

<p>
    在对变量执行 typeof 操作时，得到的结果并不是该变量的类型，而是该变量持有的值的类
型，因为 JavaScript 中的变量没有类型。
</p>

<pre class="language-javascript line-number">
    <code>
        var a = 42;
        typeof a; // "number"

        a = true;
        typeof a; // "boolean"

        // typeof 运算符总是会返回一个字符串：
        typeof typeof 42; // "string"
        // typeof 42 首先返回字符串 "number"，然后 typeof "number" 返回 "string"。
    </code>
</pre>

<pre>
undefined 和 null 常被用来表示“空的”值或“不是值”的值。

二者之间有一些细微的差别。例如：
    • null 指空值（empty value）
    • undefined 指没有值（missing value）

或者：
    • undefined 指从未赋值
    • null 指曾赋过值，但是目前没有值
    null 是一个特殊关键字，不是标识符，我们不能将其当作变量来使用和赋值。然而
    undefined 却是一个标识符，可以被当作变量来使用和赋值。
</pre>

<blockquote>
    简单标量基本类型值（字符串和数字等）通过值复制来赋值 / 传递，而复合值（对象等）
通过引用复制来赋值 / 传递。 
<br>
    JavaScript 中的引用和其他语言中的引用 / 指针不同，它们不
能指向别的变量 / 引用，只能指向值。
</blockquote>


<h2>原生函数</h2>

<pre>
    • String()
    • Number()
    • Boolean()
    • Array()
    • Object()
    • Function()
    • RegExp()
    • Date()
    • Error()
    • Symbol()——ES6 中新加入的！
</pre>
<p>原生函数可以被当作构造函数来使用，但其构造出来的对象可能会和我们设想的有所
    出入：</p>

<pre class="language-javascript line-number">
    <code>
        var a = new String( "abc" );
        typeof a; // 是"object"，不是"String"

        a instanceof String; // true
        Object.prototype.toString.call( a ); // "[object String]"

        // 通过构造函数（如 new String("abc")）创建出来的是封装了基本类型值（如 "abc"）的封 装对象
        // 请注意： typeof 在这里返回的是对象类型的子类型

        Object.prototype.toString.call( [1,2,3] );
        // "[object Array]"

        Object.prototype.toString.call( /regex-literal/i );
        // "[object RegExp]"
    </code>
</pre>

<p>
    由 于 基 本 类 型 值 没 有 .length和 .toString() 这样的属性和方法，需要通过封装对象才能访问，
    <br>
    此时 JavaScript 会<b>自动为基本类型值包装</b>（box 或者 wrap）一个封装对象：
    <br>
    <br>
    我们不需要直接使用封装对象。最好的办法是让 JavaScript 引擎自己决定什么时候应该使用封装对象。
    <br>
    换句话说，就是应该优先考虑使用 "abc" 和 42 这样的基本类型值，而非 new String("abc") 和 new Number(42)。
</p>
<pre class="language-javascript line-number">
    <code>
        var a = "abc";
        a.length; // 3
        a.toUpperCase(); // "ABC"
    </code>
</pre>
<dl>
    <dt>封装对象释疑</dt>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                var a = new Boolean( false );
                if (!a) {
                    console.log( "Oops" ); // 执行不到这里
                }
                // 我们为 false 创建了一个封装对象，然而该对象是真值（“truthy”，即总是返回 true，
                // 所以这里使用封装对象得到的结果和使用 false 截然相反
            </code>
        </pre>
    </dd>
    <dd>如果想要自行封装基本类型值，可以使用 Object(..) 函数（不带 new 关键字）</dd>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                var a = "abc";
                var b = new String( a );
                var c = Object( a );
                typeof a; // "string"
                typeof b; // "object"
                typeof c; // "object"
                b instanceof String; // true
                c instanceof String; // true
                Object.prototype.toString.call( b ); // "[object String]"
                Object.prototype.toString.call( c ); // "[object String]"

                // 一般不推荐直接使用封装对象（如上例中的 b 和 c），但它们偶尔也会派上用场
            </code>
        </pre>
    </dd>
    <dt>拆封</dt>
    <dd>如果想要得到封装对象中的基本类型值，可以使用 valueOf() 函数：</dd>
    <dd>
        <pre class="language-javascript line-number">
            <code>
                var a = new String( "abc" );
                var b = new Number( 42 );
                var c = new Boolean( true );
                a.valueOf(); // "abc"
                b.valueOf(); // 42
                c.valueOf(); // true
            
                // 在需要用到封装对象中的基本类型值的地方会发生隐式拆封。具体过程（即强制类型转换）
                var a = new String( "abc" );
                var b = a + ""; // b的值为"abc"
                typeof a; // "object"
                typeof b; // "string"
            </code>
        </pre>
    </dd>
</dl>

<h2>强制类型转换</h2>
<p>将值从一种类型转换为另一种类型通常称为类型转换（type casting），这是显式的情况；</p>
<p>隐式的情况称为强制类型转换（coercion）</p>
<blockquote>
    JavaScript 中的强制类型转换总是<b>返回标量基本类型值</b>，如字符串、数字和布尔值，不会返回对象和函数
    <br>
    “封装”，就是为标量基本类型值封装一个相应类型的对象，但这并非严格意义上的强制类型转换
    <br>
    类型转换发生在静态类型语言的编译阶段，而强制类型转换则发生在动态类型语言的运行时（runtime）
</blockquote>

<p>如果 + 的其中一个操作数是字符串（或者通过valueOf() 操作， toString()可以得到字符串），
    则执行字符串拼接；否则执行数字加法</p>
<pre class="language-javascript line-number">
    <code>
        var a = [1,2];
        var b = [3,4];
        a + b; // "1,23,4"
        // 数组的valueOf() 操作无法得到简单基本类型值，于是它转而调用 toString()

        // 在定制 valueOf() 和 toString() 方法时需要特别小心，因为这会影响强制类型转换的结果
        > var a = {
            ... valueOf: function() { return 42; },
            ... toString: function() { return 4; }
            ... };
            undefined
        > a
        { valueOf: [Function: valueOf], toString: [Function: toString] }
        > a + ''
        '42'
        > String(a)
        '4'
    </code>
</pre>


<blockquote>
    常见的误区是: “== 检查值是否相等， === 检查值和类型是否相等”
    <br>
    正确的解释是：
    “== 允许在相等比较中进行强制类型转换，而 === 不允许。”
</blockquote>

<h2>语法</h2>

<h3>对象解构</h3>
<pre class="language-javascript line-number">
    <code>
        function getData() {
            // ..
            return {
                a: 42,
                b: "foo"
            };
        }
        var { a, b } = getData();
        console.log( a, b ); // 42 "foo"

        > {a, b} = {c:1, d:2}
        { c: 1, d: 2 }
        > a
        undefined
        > {a, b} = {a:1, b:2}
        { a: 1, b: 2 }
        > a
        1
        > b
        2

        // { a, b } 实际上是 { a: a, b: b } 的简化版本，两者均可，只不过 { a, b }更简洁。
    </code>
</pre>

<h3>else if 和可选代码块</h3>
<p>事实上 JavaScript 没有 else if，但 if 和 else 只包含单条语句的时候可以省略代码块的
    { }</p>
<pre class="language-javascript line-number">
    <code>
        if (a) doSomething( a );

        // 很多 JavaScript 代码检查工具建议对单条语句也应该加上 { }，如：
        if (a) { doSomething( a ); }

        // else 也是如此，所以我们经常用到的 else if 实际上是这样的：
        if (a) {
            // ..
        }
        else {
            if (b) {
                // ..
            }
            else {
                // ..
            }
        }
    </code>
</pre>

<p>JavaScript 会自动为代码行补上缺失的分号，即自动分号插入（Automatic Semicolon Insertion， ASI）
<br>
ASI（自动分号插入）是 JavaScript 引擎的代码解析纠错机制，它会在需要的地方自动插
入分号来纠正解析错误。
</p>

<h3>try{}catch{}finally{}</h3>
<pre class="language-javascript line-number">
    <code>
        > function foo(){
            ...   try {
            .....     throw 42;
            ..... }
            ...   catch (err) {
            .....     console.log("catch an error")
                      reportError(err);
            .....   }
            ...   finally {
            .....     console.log("finally")
            .....   }
            ...   console.log("runs");
            ... }
            undefined
            > foo()
            catch an error
            finally
            runs
            undefined
    </code>
</pre>


<h1>异步和性能</h1>

<p>核心 JavaScript 技能: 如何使用闭包\如何充分利用异步</p>
<p>Promise 现在已经是 JavaScript 和 DOM 提供异步返回值的正式方法</p>
<h2>异步：现在与将来</h2>
<p>程序的一部分现在运行，而另一部分则在将来运行——现在和将来
    之间有段间隙，在这段间隙中，程序没有活跃执行</p>
<p>程序中现在运行的部分和将来运行的部分之间的关系就是异步编程的核心</p>
<p>setTimeout(..) 定时器的精度可能不高。大体说来，只能确保你的回调函数不会在指定的
    时间间隔之前运行，但可能会在那个时刻运行，也可能在那之后运行，要根据事件队列的
    状态而定。</p>
<p> ES6 中 Promise 的引
    入，因为这项技术要求对事件循环队列的调度运行能够直接进行精细控制
<br>
Promise 的异步特性是基于任务的，所以一定要清楚它和事件循环特性的关系。
</p>

<h3>并行线程</h3>

<blockquote>
    异步是关于现在和将来的时间间隙，
    <br>
    而并行是关于能够同时发生的事情, 并行计算最常见的工具就是进程和线程
</blockquote>
<p>完整运行（run-to-completion）特性: 由于 JavaScript 的单线程特性， foo()（以及 bar()）中的代码具有原子性</p>

<h3>并发</h3>
<p>“进程”同时运行（也就是说，是在同一段时间内，并不需要在同一时刻）。</p>
<p>“进程”之所以打上引号，是因为这并不是计算机科学意义上的真正
    操作系统级进程。这是虚拟进程，或者任务，表示一个逻辑上相关的运算序
    列。之所以使用“进程”而不是“任务”，是因为从概念上来讲，“进程”的
    定义更符合这里我们使用的意义。</p>

<h4>非交互</h4>
<p>如果进程间没有相互影响的话，不确定性是完全可以接受的。</p>
<p>构建程序的方式使得无论按哪种顺序执行都无所谓，因为它们是独立运行的，不会相互影
    响</p>
    <pre class="language-javascript line-number">
        <code>
            var res = {};
            function foo(results) {
                res.foo = results;
            }
            function bar(results) {
                res.bar = results;
            }

            // ajax(..)是某个库提供的某个Ajax函数
            ajax( "http://some.url.1", foo );
            ajax( "http://some.url.2", bar );
        </code>
    </pre>
<h4>交互</h4>
<p>并发的“进程”需要相互交流，通过作用域或 DOM 间接交互。
    <br>
    如果出现这样的交互，就需要对它们的交互进行协调以避免竞态的出现。
</p>
<pre class="language-javascript line-number">
    <code>
        var res = [];
        function response(data) {
            res.push( data );
        }
        // ajax(..)是某个库中提供的某个Ajax函数
        ajax( "http://some.url.1", response );
        ajax( "http://some.url.2", response );

        // 假定期望的行为是 res[0] 中放调用 "http://some.url.1" 的结果， 
        // res[1] 中放调用 "http://some.url.2" 的结果
        // 视哪个调用先完成而定
    </code>
</pre>

<p>可以协调交互顺序来处理这样的竞态条件：</p>
<pre class="language-javascript line-number">
    <code>
        // 通过简单的协调，就避免了竞态条件引起的不确定性
        var res = [];
        function response(data) {
            if (data.url == "http://some.url.1") {
                res[0] = data;
            }
            else if (data.url == "http://some.url.2") {
                res[1] = data;
            }
        }
        // ajax(..)是某个库中提供的某个Ajax函数
        ajax( "http://some.url.1", response );
        ajax( "http://some.url.2", response );
    </code>
</pre>

<p>包裹 baz() 调用的条件判断 if (a && b) 传统上称为门（gate），我们虽然不能确定 a 和 b
    到达的顺序，但是会等到它们两个都准备好再进一步打开门（调用 baz()）</p>
<pre class="language-javascript line-number">
    <code>
        var a, b;
        function foo(x) {
            a = x * 2;
            if (a && b) {
                baz();
            }
        }
        function bar(y) {
            b = y * 2;
            if (a && b) {
                baz();
            }
        }
        function baz() {
            console.log( a + b );
        }
        // ajax(..)是某个库中的某个Ajax函数
        ajax( "http://some.url.1", foo );
        ajax( "http://some.url.2", bar );
    </code>
</pre>
<p>条件判断 if (!a) 使得只有 foo() 和 bar() 中的第一个可以通过，第二个（实际上是任何
    后续的）调用会被忽略。也就是说，第二名没有任何意义！
<br>
并发交互条件有时称为竞态（race），但是更精确的叫法是门闩（latch）
</p>
<pre class="language-javascript line-number">
    <code>
        var a;
        function foo(x) {
            if (!a) {
                a = x * 2;
                baz();
            }
        }
        function bar(x) {
            if (!a) {
                a = x / 2;
                baz();
            }
        }
        function baz() {
            console.log( a );
        }
        // ajax(..)是某个库中的某个Ajax函数
        ajax( "http://some.url.1", foo );
        ajax( "http://some.url.2", bar );
    </code>
</pre>
<h4>协作</h4>
<p>并发协作（cooperative concurrency）。这里的重点不再是通过
    共享作用域中的值进行交互（尽管显然这也是允许的！）。这里的目标是取到一个长期运
    行的“进程”，并将其分割成多个步骤或多批任务，使得其他并发“进程”有机会将自己
    的运算插入到事件循环队列中交替运行</p>
<pre class="language-javascript line-number">
    <code>
        var res = [];
        // response(..)从Ajax调用中取得结果数组
        function response(data) {
            // 一次处理1000个
            var chunk = data.splice( 0, 1000 );
            // 添加到已有的res组
            res = res.concat(
                // 创建一个新的数组把chunk中所有值加倍
                chunk.map( function(val){
                    return val * 2;
                } )
            );
            // 还有剩下的需要处理吗？
            if (data.length > 0) {
                // 异步调度下一次批处理
                setTimeout( function(){
                    response( data );
                }, 0 );
            }
        }

        // ajax(..)是某个库中提供的某个Ajax函数
        ajax( "http://some.url.1", response );
        ajax( "http://some.url.2", response );

        //  setTimeout(..0)（hack）进行异步调度，基本上它的意思就是“把这个函数插入到当前事件循环队列的结尾处”。
    </code>
</pre>
<p>一旦有事件需要运行，事件循环就会运行，直到队列清空。事件循环的每一轮称为一个
    tick。用户交互、 IO 和定时器会向事件队列中加入事件</p>


<p>任意时刻，一次只能从队列中处理一个事件。执行事件的时候，可能直接或间接地引发一
    个或多个后续事件</p>


<h2>回调</h2>
<p>更好的异步模式 promise（promise 也是“承诺、希望”的意思，此处一语双关）</p>
<h3>continuation持续;继续;延续</h3>
<p>
// A 和 // B 表示程序的前半部分（也就是现在的部分），
<br>
而 // C 标识了程序的后半部分（也就是将来的部分）。前半部分立刻执行，
<br>
然后是一段时间不确定的停顿。
<br>
在未来的某个时刻，如果 Ajax 调用完成，程序就会从停下的位置继续执行后半部分
</p>
<pre class="language-javascript line-number">
    <code>
        // A
        ajax( "..", function(..){
            // C
        } );
        // B

        // 进一步简化这段代码：
        // A
        setTimeout( function(){
            // C
        }, 1000 );
        // B
    </code>
</pre>

<p>回调函数包裹或者说封装了程序的延续（continuation）</p>


<p>“他人即地狱”（萨特）这种说法，对程序员来说则是“他人的代码即地狱”。
<br>
而我深信不疑的是：“不理解自己的代码才是地狱。”
</p>

<h3>嵌套回调与链式回调</h3>


<pre class="language-javascript line-number">
    <code>
        // 一开始我们在等待 click 事件，然后等待定时器启动，然后等待 Ajax 响应返回，之后可能再重头开始。

        // 三个函数嵌套在一起构成的链，其中每个函数代表异步序列（任务，“进程”）中的一个步骤
        listen( "click", function handler(evt){
            setTimeout( function request(){
                ajax( "http://some.url.1", function response(text){
                    if (text == "hello") {
                        handler();
                    }
                    else if (text == "world") {
                        request();
                    }
                } );
            }, 500) ;
            } );

        // 这种代码常常被称为回调地狱（callback hell），
        // 有时也被称为毁灭金字塔（pyramid of doom，得名于嵌套缩进产生的横向三角形状）

        // 问题是出在嵌套上吗？是它导致跟踪异步流如此之难吗？确实，部分原因是这样。
    </code>
</pre>

<pre class="language-javascript line-number">
    <code>
        // 不用嵌套再把前面的嵌套事件 / 超时 /Ajax 的例子重写一遍

        listen( "click", handler );
        function handler() {
            setTimeout( request, 500 );
        }
        function request(){
            ajax( "http://some.url.1", response );
        }
        function response(text){
            if (text == "hello") {
                handler();
            }
            else if (text == "world") {
                request();
            }
        }
        // 这种组织形式的代码不像前面以嵌套 / 缩进的形式组织的代码那么容易识别了，
        // 但是它和回调地狱一样脆弱，易受影响。为什么？

        // 在整个代码中跳来跳去以“查看”流程
        // 真实的异步 JavaScript 程序代码要混乱得多，这使得这种追踪的难度会成倍增加
    </code>
</pre>

<h2>信任问题</h2>

<pre class="language-javascript line-number">
    <code>
        // A
        ajax( "..", function(..){
            // C
        } );
        // B
    </code>
</pre>
<p>
    // A 和 // B 发生于现在，在 JavaScript 主程序的直接控制之下。
    <br>
    而 // C 会延迟到将来发生，并且是在第三方的控制下——在本例中就是函数 ajax(..)
</p>
<p>
    有时候 ajax(..)（也就是你交付回调 continuation 的第三方）不是你编写的代码，也不在你的直接控制下。多数情况下，它是某个第三方提供的工具。
</p>
<p>
    控制反转（inversion of control）， 也就是把自己程序一部分的执行控制交给某个第三方。
    <br>
    在你的代码和第三方工具（一组你希望有人维护的东西）之间有一份并没有明确表达的契约。
</p>

<h3>不只是别人的代码</h3>
<p>地缘政治原则：“信任，但要核实。”</p>
<p>不只是针对外部代码，甚至是我们知道在我们自己控制下的代码</p>

<h2>省点回调</h2>

<pre class="language-javascript line-number">
    <code>
        function success(data) {
            console.log( data );
        }
        function failure(err) {
            console.error( err );
        }
        ajax( "http://some.url.1", success, failure );

        // 这种设计下， API 的出错处理函数 failure() 常常是可选的，
        // 如果没有提供的话，就是假定这个错误可以吞掉。
    </code>
</pre>


<pre class="language-javascript line-number">
    <code>
        function response(err,data) {
            // 出错？
            if (err) {
                console.error( err );
            }
            // 否则认为成功
            else {
                console.log( data );
            }
        }
        ajax( "http://some.url.1", response );
    </code>
</pre>

<blockquote>
    一条非常有效的建议：<b>永远异步调用回调</b>，即使就在事件循环的下一轮，这样，所有回调就都是可预测的异步调用了。
</blockquote>

<pre class="language-javascript line-number">
    <code>
        function result(data) {
            console.log( a );
        }
        var a = 0;
        // 这段代码会打印出 0（同步回调调用）还是 1（异步回调调用）呢？这要视情况而定
        ajax( "..pre-cached-url..", result );
        a++;
    </code>
</pre>

<p>
    如果你不确定关注的 API 会不会永远异步执行怎么办呢？
    <br>
    可以创建一个类似于这个“验证概念”版本的 asyncify(..) 工具：
</p>

<pre class="language-javascript line-number">
    <code>
        function asyncify(fn) {
            var orig_fn = fn,
            intv = setTimeout( function(){
                intv = null;
                if (fn) fn();
            }, 0 );
            fn = null;
            return function() {
                // 触发太快，在定时器intv触发指示异步转换发生之前？
                if (intv) {
                    fn = orig_fn.bind.apply(
                        orig_fn,
                        // 把封装器的this添加到bind(..)调用的参数中，
                        // 以及克里化（currying）所有传入参数
                        [this].concat( [].slice.call( arguments ) )
                    );
                }
                // 已经是异步
                else {
                // 调用原来的函数
                orig_fn.apply( this, arguments );
                }
            };
        }



        // 可以像这样使用 asyncify(..)：
        function result(data) {
            console.log( a );
        }
        var a = 0;

        ajax( "..pre-cached-url..", asyncify( result ) );
        a++;

        // 不管这个 Ajax 请求已经在缓存中并试图对回调立即调用，还是要从网络上取得，进而在将来异步完成，这段代码总是会输出 1，
        // 而不是 0——result(..) 只能异步调用，这意味着 a++ 有机会在 result(..) 之前运行
    </code>
</pre>

<h2>Promise</h2>
<blockquote>
    回调表达程序异步和管理并发的两个主要缺陷：缺乏顺序性和可信任性。
</blockquote>
<p>
    回调函数来封装程序中的 continuation，然后把回调交给第三方（甚至可
能是外部代码），接着期待其能够调用回调，实现正确的功能。
<br>
表达的意思是：“这是将来要做的事情，要在当前的步骤完成之后发生。”
</p>

<p>
    不把自己程序的continuation 传给第三方，而是希望第三方给我们提供了解其任务何时结束的能力，然后由
我们自己的代码来决定下一步做什么，
<br>
这种范式就称为 Promise
</p>


<h3>什么是 Promise</h3>
<h4>未来值</h4>
<ol>
    <li>通过下订单并付款，我已经发出了一个对某个值（就是那个汉堡）的请求</li>
    <li>订单号就是一个 IOU（I owe you，我欠你的） 承诺（promise）</li>
    <li>我听到服务员在喊“订单 113”，然后愉快地拿着收据走到柜台，把收据交给收银员，换来了我的芝士汉堡</li>
</ol>
<p>一旦我需要的值准备好了，我就用我的承诺值（value-promise）换取这个值本身</p>

<pre class="language-javascript line-number">
    <code>
        // 现在值与将来值

        // 把 x 和 y 当作未来值，并且表达了一个运算 add(..)
        function add(getX,getY,cb) {
            var x, y;
            getX( function(xVal){
                x = xVal;
                // 两个都准备好了？
                if (y != undefined) {
                    cb( x + y ); // 发送和
                }
            } );
            getY( function(yVal){
                y = yVal;
                // 两个都准备好了？
                if (x != undefined) {
                    cb( x + y ); // 发送和
                }
            } );
        }
        // fetchX() 和fetchY()是同步或者异步函数
        add( fetchX, fetchY, function(sum){
        console.log( sum ); // 是不是很容易？
        } );
    </code>
</pre>


<pre class="language-javascript line-number">
    <code>
        // Promise 值


        function add(xPromise,yPromise) {
            // Promise.all([ .. ])接受一个promise数组并返回一个新的promise，
            // 这个新promise等待数组中的所有promise完成
            return Promise.all( [xPromise, yPromise] )
            // 这个promise决议之后，我们取得收到的X和Y值并加在一起
            .then( function(values){
                // values是来自于之前决议的promisei的消息数组
                return values[0] + values[1];
            } );
        }

        // fetchX()和fetchY()返回相应值的promise，可能已经就绪，
        // 也可能以后就绪
        add( fetchX(), fetchY() )

        // 我们得到一个这两个数组的和的promise
        // 现在链式调用 then(..)来等待返回promise的决议
        .then( function(sum){
            console.log( sum ); // 这更简单！
        } );


        // 这段代码中有两层 Promise

        // 第一层fetchX() 和 fetchY() 是直接调用的，它们的返回值（promise ！）被传给 add(..)。

        // 第二层是 add(..)（通过 Promise.all([ .. ])）创建并返回的 promise。
        // 我们通过调用then(..) 等待这个 promise。
    </code>
</pre>

<p>
    通过 Promise，调用 then(..) 实际上可以接受两个函数，第一个用于完成情况，第二个用于拒绝情况：
</p>
<p>如果在获取 X 或 Y 的过程中出错，或者在加法过程中出错， add(..) 返回的就是一个被拒
    绝的 promise，传给 then(..) 的第二个错误处理回调就会从这个 promise 中得到拒绝值。</p>
<pre class="language-javascript line-number">
    <code>
        add( fetchX(), fetchY() )
        .then(
            // 完成处理函数
            function(sum) {
                console.log( sum );
            },
            // 拒绝处理函数
            function(err) {
                console.error( err ); // 烦！
            }
        );
    </code>
</pre>

<blockquote>
    从外部看，由于 Promise 封装了依赖于时间的状态——等待底层值的完成或拒绝，
    所以Promise 本身是与时间无关的。
<br>
    Promise 是一种封装和组合未来值的易于复用的机制。
</blockquote>






{% endblock content %}
